<?php

require_once 'Basic.php';

class InvalidActionValueException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class ActionKeyNotFoundException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class InvalidUserActionException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class FormAlreadyProcessedException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class FormNotProcessedException extends Exception {
  public function __construct($message = "", $code = 0, $previous = null) {
    parent::__construct($message, $code, $previous);
  }
}

class StdForm {

  const ACTION_KEY = "sf_a";
  const ACTION_VALUE_RECEIVE = "r";
  const ACTION_VALUE_PRINT = "p";
  
  public static function decoreHref($href, $value) {
    if(strstr($href, "?") === false) {
      return $href . "?" . StdForm::ACTION_KEY . "=" . $value;
    }
    else {
      return $href . "&" . StdForm::ACTION_KEY . "=" . $value;
    }
  }
  
  private $entity;
  private $controlData;
  private $userInput;
  private $preHtml;
  private $successHtml;
  private $formAction;
  private $processResult;

  public function __construct(BasicEntity $entity, $controlData, $userInput, $preHtml, $successHtml, $formAction) {
    $this->entity = $entity;
    $this->controlData = $controlData;
    $this->userInput = $userInput;
    $this->preHtml = $preHtml;
    $this->successHtml = $successHtml;
    $this->formAction = StdForm::decoreHref($formAction, StdForm::ACTION_VALUE_RECEIVE);
    $this->processResult = null;
  }
  
  private function getFormHtml() {
    $out = $this->preHtml !== null ?
           "<p class='StdFormPreText'>" . $this->preHtml . "</p>" . PHP_EOL :
           "";
    $htmlClass = "StdForm";
    $out .= "<form class='${htmlClass}' method='post' action='" . $this->formAction . "'><div class='border'>" . PHP_EOL;
    foreach($this->entity->fields() as $field) {
      $out .= $field->getHtml() . PHP_EOL;
    }
    return $out . '<p>&nbsp;<input type="submit" id="submit" value="Enviar"></p></div></form>';
  }
  
  public function receiving() {
    return isset($this->controlData[StdForm::ACTION_KEY]) &&
           $this->controlData[StdForm::ACTION_KEY] == StdForm::ACTION_VALUE_RECEIVE;
  }
  
  /**
   * @return string O código HTML para este formulário.
   * 
   */
  public function getHtml() {
    if($this->processResult === null) {
       throw new FormNotProcessedException();
    }
    else if($this->processResult[StdForm::ACTION_KEY] === StdForm::ACTION_VALUE_PRINT) {
      return $this->getFormHtml() . "<div style='clear:both;'></div>";
    }
    else {
      $result = $this->processResult["result"];
      if($result === true) {
        return $this->successHtml === null ?
               "" :
               '<p class="successMessage">' . $this->successHtml . '</p>' . PHP_EOL .
               "<div style='clear:both;'></div>";
      }
      else {
        $out = $this->getFormHtml();
        $type = gettype($result);
        $out .= "<div class='errorMessages'>";
        if($type === "string") {
          $out .= "<p class='errorMessage'>"  . $result . "</p>";
        }
        else {
          foreach($result as $errorMessage) {
            $out .= "<p class='errorMessage'>"  . $errorMessage . "</p>";
          }
        }
        $out .= "</div><div style='clear:both;'></div>";
        return $out;
      }
    }
  }
  
  /**
   * @return boolean Se ocorreu tudo bem ou se houve erros.
   * @throws FormAlreadyProcessedException Caso o form já foi processado.
   * @throws ActionKeyNotFoundException Caso não haja uma ação especificada nos dados de controle.
   * @throws InvalidActionValueException Caso a ação especificada seja inválida.
   * @throws Exception Quando um erro não categorizado for lançado. Isso deve ser alterado para algo mais descritivo.
   */
  public function processForm() {
    if($this->processResult !== null) {
      throw new FormAlreadyProcessedException();
    }
    else {
      try {
        if(!isset($this->controlData[StdForm::ACTION_KEY])) {
          throw new ActionKeyNotFoundException();
        }
        $action = $this->controlData[StdForm::ACTION_KEY];
        if($action === StdForm::ACTION_VALUE_RECEIVE) {
          $this->processResult = array(
            StdForm::ACTION_KEY => StdForm::ACTION_VALUE_RECEIVE
          );
          $result = $this->entity->request($this->userInput);
          $this->processResult["result"] = $result;
          $type = gettype($result);
          if($type !== "array" && $type !== "string"  && $result !== true) {
            throw new Exception("Erro Interno: ${type} foi retornado por request: " .
                                $this->entity->actionDescription() . ".");
          }
          return $result === true;
        }
        else if($action === StdForm::ACTION_VALUE_PRINT) {
          $this->processResult = array(
            StdForm::ACTION_KEY => StdForm::ACTION_VALUE_PRINT
          );
          return true;
        }
        else {
          throw new InvalidActionValueException($action);
        }
      }
      catch(Exception $ex) {
        $this->processResult = null;
        throw $ex;
      }
    }
  }

}
